import { IHeapNode, serializer } from '@memlab/core'
import { ArkNodesMap, ArkTracePathItem } from './ArkTracePath';
import { Log } from '../utils/Log';
import { ConsoleData, ConsoleTableData, TextData, JsonData, HtmlData, OutputCfg } from '../utils/Output';

/**
 * Ark Serializer
 */
export class ArkSerializer {    
    private static getNodesTableTitle(cfg?: OutputCfg): string[] {
        return [
            cfg?.node?.enableId ? 'id' : '',
            cfg?.node?.enableType ? 'type' : '',
            cfg?.node?.enableName ? 'name' : '',
            cfg?.node?.enableSelfSize ? 'self size' : '',
            cfg?.node?.enableRetainedSize ? 'retained size' : ''
        ].filter(item => !!item);
    }

    private static getNodesArrayData(nodes: IHeapNode[], cfg?: OutputCfg) : string[][] | undefined {
        if (nodes.length === 0) {
            return undefined;
        }
        let title = ArkSerializer.getNodesTableTitle(cfg);
        let size = 0;
        let body = nodes.reduce((acc, node) => {
            if (cfg?.node?.enableId) {
                acc += `,${node.id}`;
                size++;
            }
            if (cfg?.node?.enableType) {
                acc += `,${node.type}`;
                size++;
            }
            if (cfg?.node?.enableName) {
                acc += `,${node.name}`;
                size++;
            }
            if (cfg?.node?.enableSelfSize) {
                acc += `,${node.self_size}`;
                size++;
            }
            if (cfg?.node?.enableRetainedSize) {
                acc += `,${node.retainedSize}`;
                size++;
            }  
            return acc;
        }, '').split(',').filter(item => !!item);
        let arr = title.concat(...body);
        let data = arr.reduce((acc, _, i) => {
            if (i % size === 0) {
                acc.push(arr.slice(i, i + size));
            }
            return acc;
        }, [] as string[][]);
        return data;
    }

    private static getNodesJsonArray(nodes: IHeapNode[], cfg?: OutputCfg) : any[] {
        if (nodes.length === 0) {
            return [];
        }
        const result: any[] = [];
        nodes.forEach(node => {
            const jsonNode: any = {};
            if (cfg?.node?.enableId) {
                jsonNode["id"] = node.id;
            }
            if (cfg?.node?.enableType) {
                jsonNode["type"] = node.type;
            }
            if (cfg?.node?.enableName) {
                jsonNode["name"] = node.name;
            } 
            if (cfg?.node?.enableSelfSize) {
                jsonNode["selfSize"] = node.self_size;
            }
            if (cfg?.node?.enableRetainedSize) {
                jsonNode["retainedSize"] = node.retainedSize;
            }
            result.push(jsonNode);
        });
        return result;
    }

    public static getTextDataByNodes(nodes: IHeapNode[], cfg?: OutputCfg) : TextData | undefined {
        if (nodes.length === 0) {
            return undefined;
        }
        let desc = cfg?.desc || '';
        let data = nodes.reduce((acc, node) => {
            if (cfg?.node?.enableName) {
                acc += `[${node.name}]`;
            }
            if (cfg?.node?.enableType) {
                acc += ` (${node.type})`;
            }
            if (cfg?.node?.enableId) {
                acc += ` @${node.id}`;
            }
            if (cfg?.node?.enableSelfSize) {
                acc += ` [${node.self_size}]`;
            }
            if (cfg?.node?.enableRetainedSize) {
                acc += ` [${node.retainedSize}]`;
            }
            acc += '\n';
            return acc;
        }, '');
        return new TextData(desc, data);
    }

    public static getJsonDataByNodes(
        nodes: IHeapNode[],
        cfg?: OutputCfg
    ): JsonData { 
        let jsonArr = ArkSerializer.getNodesJsonArray(nodes, cfg);
        let desc = cfg?.desc || {};
        return new JsonData(desc, jsonArr);
    }

    public static getConsoleDataByNodes(
        isTable: boolean,
        nodes: IHeapNode[],
        cfg?: OutputCfg) : ConsoleData {
        if (isTable) {
            let titleArr = ArkSerializer.getNodesTableTitle(cfg);
            let jsonArr = ArkSerializer.getNodesJsonArray(nodes, cfg);
            const tabalData = new ConsoleTableData(titleArr, jsonArr);
            return new ConsoleData(isTable, tabalData);
        } else {
            let textData = ArkSerializer.getTextDataByNodes(nodes, cfg);
            return new ConsoleData(isTable, textData?.data);
        }
    }

    public static getHtmlDataByNodes(
        nodes: IHeapNode[],
        cfg: OutputCfg) : HtmlData {
        // TODO
        let jsonArr = ArkSerializer.getNodesJsonArray(nodes, cfg);
        let desc = cfg?.desc || '';
        return new HtmlData(desc, jsonArr);
    }

    public static getNodeTracePathString(path: ArkTracePathItem | undefined) : string {
        if (!path) {
            Log.error('meminsight', ArkSerializer.name, 'trace path not found.');
            return '';
        }
        let snapshot = path.node?.snapshot;
        if (!snapshot) {
            Log.error('meminsight', ArkSerializer.name, 'snapshot not found.');
            return '';
        }
        return serializer.summarizePath(path, new Set<number>(), snapshot);
    }

    public static getArkNodesMapString(arkNodesMap: ArkNodesMap) : string {
        // TODO:
        return '';
    }
}